<?php

namespace app\extensions\lib\push;

use app\exception\ApiException;
use app\extensions\lib\RedisLock;
use app\model\Package;
use app\model\TokenInfo;
use app\queue\redis\shushu\DeviceEvent;
use Firebase\JWT\JWT;
use support\Db;
use support\Log;
use support\Redis;

class HmsPush extends BasePush
{
    const ACCESS_TOKEN_KEY = 'ios_push_access_token_';
    const LOCK_KEY = 'lock_ios_access_token_';
    const ASYNC_KEY = '_push_list_';
    const SUCCESS_CODE = '80000000';

    public static function push($tokenInfo, array $message,$request_id='')
    {
        self::blackSkip($tokenInfo,$request_id);
        self::requestRecall($tokenInfo,$request_id);
        if(!$tokenInfo['tokens']) return self::defaultReturn();;
        $authToken = self::getPackageIdentification($tokenInfo['package_id'], $tokenInfo['is_async']);
        return self::send($tokenInfo['package_id'], $authToken, $tokenInfo, $message,false,$request_id);
    }

    public static function getPackageIdentification($package_id, $is_async)
    {

        $config = Package::getHuaWeiConfig($package_id);
        if (!isset($config['package_name'])) {
            throw new ApiException("包：{$package_id}无对应 包名");
        }
        if (!isset($config['app_id']) || !isset($config['app_secret'])) {
            throw new ApiException("包：{$package_id}无对应 app_id,密钥id");
        }

        $cacheKey = self::getCacheKey($package_id, $is_async, $config);
        $oldValue = Redis::get($cacheKey);
        if ($oldValue) {
            return $oldValue;
        }

        $RedisLock = new RedisLock();
        $lockKey = self::ACCESS_TOKEN_KEY;;
        //使用悲观锁
        $authorization = $RedisLock->actionLock($lockKey, function () use ($package_id, $cacheKey, $config) {
            $oldValue = Redis::get($cacheKey);
            if ($oldValue) return $oldValue;//其他锁已经生成就不用了

            $app_id = $config['app_id'];
            $app_secret = $config['app_secret'];
            $payload = [
                'grant_type' => 'client_credentials',
                'client_secret' => $app_secret,
                'client_id' => $app_id
            ];

            $data = curl('https://oauth-login.cloud.huawei.com/oauth2/v3/token', $payload);
            $data = json_decode($data, true) ?: $data;
            if (isset($data['expires_in']) && isset($data['access_token'])) {

            } else {
                Log::channel('hms')->error('getAccessToken_error' . json_encode($data));
            }
            $time = $data['expires_in'] - 60;
            Redis::set($cacheKey, $data['access_token']);
            Redis::expire($cacheKey, $time);
            return $data['access_token'];
        });

        return $authorization;
    }

    public static function send($package_id, $authToken, $tokenInfo, array $message, $is_sandbox = false,$request_id='')
    {
        $tokens = $tokenInfo['tokens'];
        $tokens = array_filter(explode(',', $tokens));//一次最多可填1000个Push Token。
        $udids = $tokenInfo['udids'];
        $udids = array_filter(explode(',', $udids));
        $tokensChunk = array_chunk($tokens, 1000);
        $udidsChunk = array_chunk($tokens, 1000);
        $header = [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $authToken,
        ];

        //消息体信息
        $title = $message['message']['notification']['title'] ?? '';
        $content = $message['message']['notification']['body'] ?? '';
        $message_type = self::getMessageType($message);
        $image = $message['message']['notification']['image'] ?? '';
        $image2 = $message['message']['android']['notification']['image'] ?? '';
        if(!$image && $image2) $image = $image2;

        $Event = new DeviceEvent();
        $config = Package::getHuaWeiConfig($package_id);
        $message['validate_only'] = false;
        $url = 'https://push-api.cloud.huawei.com/v1/' . $config['app_id'] . '/messages:send';

        $sockets = [];
        $eventIds = [];
        $multiSockets = curl_multi_init();
        foreach ($tokensChunk as $key => $token) {
            $sockets[$key] = curl_init();
            $message['message']['token'] = $token;
            $eventIds[$key] = $eventId = self::getEventId('','',$package_id,$key);//事件Id
            $message['message']['data']['message_id'] = $eventId;//透传消息id
            if(env('APP_ENV')!='production'){
                dump($message);
            }
            curl_setopt_array($sockets[$key], array(
                // 2.带上deviceToken向APNs服务器请求
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 5,
                CURLOPT_CUSTOMREQUEST => "POST",
                // 3.把推送的payload放入请求体+设置请求头
                CURLOPT_POSTFIELDS => json_encode($message),
                CURLOPT_HTTPHEADER => $header,
            ));
            curl_multi_add_handle($multiSockets, $sockets[$key]);
        }
        $running = null;
        $request_time = date('Y-m-d H:i:s');
        do {
            curl_multi_exec($multiSockets, $running);
        } while ($running > 0);

        $valid = $error = $success = 0;
        $delToken = [];
        foreach ($sockets as $key => $h) {
            $info = curl_getinfo($h);
            $eventId = $eventIds[$key];
            $total_time = (($info['total_time'] ?? 0) * 1000) . 'ms';
            $responseStr = curl_multi_getcontent($h);
            $response = json_decode($responseStr, true);
            $code = '';
            $status = 0;
            $tokens = $tokensChunk[$key] ?? [];
            $udids = $udidsChunk[$key] ?? [];
            $tokenCount = count($tokens);
            $statusIllegal = [];
            $prefixLog = "$request_id:count=$tokenCount===time:$total_time". json_encode($response);
            if (isset($response['code'])) {
                $code = $response['code'];
                if ($response['code'] == self::SUCCESS_CODE) {
                    $status = 1;
                    $success += $tokenCount;
                    Log::channel('hms')->info($prefixLog);
                } else {
                    //部分成功
                    if ($response['code'] == '80100000') {
                        $statusInfo = json_decode($response['msg'] ?? '', true) ?: [];
                        $statusSuccess = $statusInfo['success'] ?? 0;
                        $statusFailure = $statusInfo['failure'] ?? 0;
                        $statusIllegal = $statusInfo['illegal_tokens'] ?? [];//不合法的token
                        $success += $statusSuccess;
                        $error += $statusFailure;
                        $valid += count($statusIllegal);
                        $delToken = array_merge($delToken, $statusIllegal);//累计delToken

                        Log::channel('hms')->info($prefixLog . ":count={$statusSuccess},failure={$statusFailure}");
                    } else {
                        $error += $tokenCount;
                        Log::channel('hms')->warning($prefixLog .":count={$tokenCount}");
                    }

                }
            } else {
                Log::channel('hms')->error($prefixLog . ':' . '华为判断错误:' . $responseStr);
            }

            curl_multi_remove_handle($multiSockets, $h);

            //设备消息记录上传
            $other = json_decode(Redis::get(Push::getPayloadKey($request_id)),true) ?:[];
            foreach ($udids as $k =>$oudid){
                $otoken = $tokens[$k] ?? '';
                $properties = [
                        'title' => $title,
                        'content' => $content,
                        'image' => $image,
                        'message_type' => $message_type,
                        'request_id' => $request_id,
                        '#ip' => '',
                        'os_id' => '3',
                        'package_id' => $package_id,
                        'token' => $otoken,
                        'code' => strval($code),
                        'status' => strval($status ?: (in_array($otoken, $statusIllegal) ? 0 : 1)),
                        'sdk_info' => ['app_id' => $config['app_id']],
                        'result' => $responseStr,
                        'request_time' => $request_time
                    ] + $other;
                \Webman\RedisQueue\Redis::send($Event->queue,[
                    'package_id' => $package_id,
                    'params' => [
                        'track_update',
                        '',
                        $oudid,
                        $properties,
                        BasePush::EVENT_DEVICE_PUSH_LOG,
                        $eventId.'_'.$oudid,
                    ],
                ]);
            }
        }

        if (is_resource($multiSockets)) {
            curl_multi_close($multiSockets);
        }
        if (!empty($sockets)) {
            foreach ($sockets as $val) {
                if (is_resource($val)) {
                    curl_close($val);
                }
            }
        }
        if ($delToken) {
            $TokenInfo = new TokenInfo();
            $TokenInfo->refreshInvalid($delToken);
        }
        return compact('success','error','valid');

    }

    private static function getCacheKey($package_id, $is_async, $config)
    {
        $cacheKey = self::ACCESS_TOKEN_KEY . $is_async . '_' . ($config['app_id'] ?? '');
        return $cacheKey;
    }

    public static function getEventId($udid,$eventId='',$package_id = '',$uniKey=''){
        if(!$eventId){
            $eventId = getEventId($package_id,uniqid($uniKey));
        }
        if($udid) return $eventId.'_'.$udid;
        return $eventId;
    }

    private static function getMessageType(array $message)
    {
        $type = 1;
        if(isset($message['message']['notification'])){
            $type = 0;
        }
        if(isset($message['message']['android']['notification'])){
            $type = 0;
        }
        return $type;
    }
}